官方文档中实现了视觉过渡效果,见 扩展默认主题 | VitePress
但是它的实现与布局混在了一起,且没有传递插槽,所以本文实现了这两点
- 将 ViewTrans.vue 组件剥离出来
- 传递所有插槽
组件 .vitepress/theme/components/ViewTrans.vue
源码为
vue
<script setup lang="ts">
import { useData } from 'vitepress'
import { nextTick, provide } from 'vue'
const { isDark } = useData()
const enableTransitions = () =>
'startViewTransition' in document &&
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
if (!enableTransitions()) {
isDark.value = !isDark.value
return
}
const clipPath = [
`circle(0px at ${x}px ${y}px)`,
`circle(${Math.hypot(
Math.max(x, innerWidth - x),
Math.max(y, innerHeight - y)
)}px at ${x}px ${y}px)`
]
await document.startViewTransition(async () => {
isDark.value = !isDark.value
await nextTick()
}).ready
document.documentElement.animate(
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
{
duration: 300,
easing: 'ease-in',
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
}
)
})
</script>
<template>
<slot />
</template>
<style>
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
::view-transition-old(root),
.dark::view-transition-new(root) {
z-index: 1;
}
::view-transition-new(root),
.dark::view-transition-old(root) {
z-index: 9999;
}
.VPSwitchAppearance {
width: 22px !important;
}
.VPSwitchAppearance .check {
transform: none !important;
}
</style>
在 .vitepress/theme/index.ts
中注册全局组件
ts
import ViewTrans from "./components/ViewTrans.vue"
export default {
Layout: () => {
// return h(DefaultTheme.Layout, null, {
return h(MyLayout, null, {
// 插槽配置
})
},
async enhanceApp({ app, router, siteData }) {
// 注册全局组件
app.component('ViewTrans', ViewTrans)
},
} satisfies Theme
在 MyLayout.vue
中引入,注意要传递插槽,否则 index.ts
中的插槽配置无法启用,
布局组件 .vitepress/layouts/MyLayout.vue
源码为
vue
<script setup lang="ts">
import DefaultTheme from 'vitepress/theme'
</script>
<template>
<!-- 视觉过渡组件 -->
<ViewTrans>
<DefaultTheme.Layout>
<!-- 传递所有插槽 -->
<template v-for="(_, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}" />
</template>
</DefaultTheme.Layout>
</ViewTrans>
</template>